<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=edge，chrome=1">

    <!-- ___CSS_TEMPLATE___ -->

    <style>
        [v-cloak] {
            display: none !important;
        }
    </style>
    <style>
        @media screen and (min-width: 914px) {
            body {
                width: 96%;
                margin: 10px auto;
            }
        }

        /* Webkit */
        ::-webkit-scrollbar {
            background: #f7f7f9;
            width: 8px;
            height: 8px;
        }

        ::-webkit-scrollbar-thumb {
            background: #e5e5e5;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: #bababa
        }

        /* Firefox */
        * {
            scrollbar-color: #e5e5e5 #f7f7f9;
            scrollbar-width: thin;
        }

        .debug {
            padding-top: 25px;
            padding-right: 20px;
            font-size: 20px;
            float: right;
        }

        .download {
            padding-top: 25px;
            padding-right: 20px;
            font-size: 20px;
            float: right;
        }

        pre {
            white-space: pre-wrap;
            word-wrap: break-word;
            font-size: 85%;
            line-height: 1.45;
            background-color: #f6f8fa;
        }

        @-moz-document url-prefix() {
            pre {
                margin-top: 12px
            }
        }

        code,
        textarea {
            font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
        }

        .project-name-version {
            float: right;
            margin-bottom: 6px;
            font-size: 12px;
            color: #e5e5e5;
            text-decoration: none;
        }

        .copyResponse {
            margin-top: 54px;
            padding-left: 6px;
            padding-right: 20px;
            font-size: 20px;
            float: right;
        }
    </style>
    <link rel="icon" href="static/icon/book.svg" type="image/x-icon">
    <link rel="shortcut icon" href="static/icon/book.svg" type="image/x-icon">

    <title>Documentation</title>
</head>

<body>
<div id="app" v-cloak>
    <el-card class="box-card">
        <el-container>
            <el-header>
                {{ titleVersion }}
                <p style="color: #b7b7b7;font-size: 14px;">
                    {{ description }}
                    <el-button class="download" type="text" icon="el-icon-download" @click="download"></el-button>
                    <el-button class="debug" type="text" :icon="debugShowIcon" @click="debugShow"></el-button>
                </p>
            </el-header>
            <el-main v-loading="loading" :style="docDisplay">
                <el-tabs v-model="headerIndex" @tab-click="">
                    <el-tab-pane :label="item.label" v-for="(item,k) in treeData">
                        <el-row>
                            <el-col :span="8">
                                <el-input placeholder="Filter keyword" v-model="treeFilterText"
                                          style="padding-bottom:10px">
                                </el-input>
                                <div :style="menuStyle">
                                    <el-tree class="filter-tree" :data="item.children"
                                             node-key="id" highlight-current :filter-node-method="treeFilterNode"
                                             ref="apiTree" @node-click="treeNodeClick">
                                    </el-tree>
                                </div>
                            </el-col>
                            <el-col :span="16" style="padding-left:20px">
                                <div :style="contentStyle">
                                    <article class="markdown-body">
                                        <div v-html="markdown_text"></div>
                                    </article>
                                </div>
                            </el-col>
                        </el-row>
                    </el-tab-pane>
                </el-tabs>
            </el-main>
            <el-main :style="debugDisplay">
                <el-card class="box-card">
                    <el-divider content-position="left">Method && URL</el-divider>
                    <el-select v-model="methodValue" placeholder="Select" style="width:8%;min-width:130px;">
                        <el-option v-for="item in methodOptions" :key="item.value" :label="item.label"
                                   :value="item.value">
                        </el-option>
                    </el-select>
                    <el-select v-model="hostValue" filterable allow-create clearable default-first-option
                               placeholder="Select" style="width:20%;min-width:160px;">
                        <el-option v-for="item in hostOptions" :key="item.value" :label="item.label"
                                   :value="item.value">
                        </el-option>
                    </el-select>
                    <el-select v-model="urlValue" filterable allow-create clearable default-first-option
                               placeholder="Select" style="width:50%;;min-width:209px;">
                        <el-option v-for="item in urlOptions" :key="item.value" :label="item.label"
                                   :value="item.value">
                        </el-option>
                    </el-select>
                    <el-button type="primary" style="width:6%;min-width:80px;" @click="sendRequest"
                               :loading="loading">Send
                    </el-button>
                </el-card>
                <el-card class="box-card" style="margin-top:10px">
                    <el-divider content-position="left">Headers</el-divider>
                    <el-row>
                        <el-input v-model="headerNameInput" size="small" style="width:20%;min-width:160px;"
                                  placeholder="name" clearable>
                        </el-input>
                        <el-input v-model="headerValueInput" size="small" style="width:20%;min-width:160px;"
                                  placeholder="value" clearable>
                        </el-input>
                        <el-button type="primary" size="small" style="width:60px;" @click="addHeader">Add
                        </el-button>
                    </el-row>
                    </br>
                    <el-row>
                        <el-tag v-for="tag in headerTags" :key="tag.name" closable :type="tag.type"
                                style="margin-left: 10px;" @close="delHeader(tag)">
                            {{ tag.name.length>50?tag.name.substring(0,49)+"...":tag.name}}
                        </el-tag>
                    </el-row>
                </el-card>
                <el-card class="box-card" style="margin-top:10px">
                    <el-divider content-position="left">Body</el-divider>
                    <el-row>
                        <el-input type="textarea" :autosize="{ minRows: 6}" placeholder="Request Body"
                                  v-model="bodyTextarea">
                        </el-input>
                    </el-row>
                </el-card>
                <el-card class="box-card" style="margin-top:10px">
                    <el-divider content-position="left">Response</el-divider>
                    <el-button class="copyResponse" type="text" icon="el-icon-document-copy" @click="copyResponse">
                    </el-button>
                    <el-tabs type="card" v-model="responseTabsActiveName">
                        <el-tab-pane label="Headers" name="Headers">
                            <div id="responseHeaderText">
                                <pre><code>&nbsp;</code></pre>
                            </div>
                        </el-tab-pane>
                        <el-tab-pane label="Response" name="Response">
                            <div id="responseResponseText">
                                <pre><code>&nbsp;</code></pre>
                            </div>
                        </el-tab-pane>
                        <el-tab-pane label="Preview" name="Preview">
                            <div id="responsePreviewText">
                                <pre><code>&nbsp;</code></pre>
                            </div>
                        </el-tab-pane>
                    </el-tabs>
                </el-card>
            </el-main>
        </el-container>
        <a class="project-name-version" href="https://gitee.com/heanny/flask-docs.git"
           target="_blank">{{ PROJECT_NAME }}
            v{{ PROJECT_VERSION }}</a>
    </el-card>
</div>
</body>

<!-- ___JS_TEMPLATE___ -->

<script>
    new Vue({
        el: "#app",
        data: {
            loading: false,
            headerIndex: "",
            treeFilterText: "",
            treeData: {},
            treeDefaultProps: {
                label: "full_name",
                children: "children"
            },
            defaultProps: {
                children: 'children',
                label: 'label'
            },
            PROJECT_NAME: "",
            PROJECT_VERSION: "",
            markdown_text: "",
            title: "Documentation",
            version: "1.0.0",
            description: "",
            titleVersion: "Documentation",
            noDocText: "No documentation found for this Api",
            menuContentStyle: "padding-right:10px;overflow-y:auto;max-height:",
            menuStyle: "padding-right:10px;overflow-y:auto;max-height:670px",
            contentStyle: "padding-right:10px;overflow-y:auto;max-height:720px",
            docDisplay: "display:block",
            debugDisplay: "display:none",
            debugShowIcon: "el-icon-picture-outline-round",
            methodList: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT", "PATCH"],
            methodOptions: [],
            methodValue: "GET",
            hostOptions: [],
            hostValue: "http://127.0.0.1",
            hostValueBak: "",
            urlOptions: [],
            urlValue: "",
            urlValueBak: "",
            headerNameInput: "",
            headerValueInput: "",
            headerTagsTypeDict: {0: "primary", 1: "success", 2: "info", 3: "warning", 4: "danger"},
            headerTags: [],
            bodyTextarea: "",
            responseTabsActiveName: "Headers",
            responseTabsActiveDict: {
                "Headers": "responseHeaderText",
                "Response": "responseResponseText",
                "Preview": "responsePreviewText"
            }
        },
        created: function () {
            this.changeWindowSize()

            document.title = this.title
            // document.getElementById("md").innerHTML = marked("# " + this.title)
            this.markdown_text = marked("# " + this.title)
            this.makeMethodOptions()
        },
        mounted: function () {
            this.getData()
            this.getHostCache()
            this.getHeaderCache()
            this.pageShow()

            window.onresize = () => {
                this.changeWindowSize()
            }
        },
        methods: {
            changeWindowSize() {
                let screenHeightMenu = window.innerHeight - 220
                let screenHeightContent = screenHeightMenu + 50
                this.menuStyle = this.menuContentStyle + screenHeightMenu + "px"
                this.contentStyle = this.menuContentStyle + screenHeightContent + "px"
            },
            getData() {
                this.loading = true
                axios.get("data").then(res => {
                        this.treeData = res.data.data
                        this.PROJECT_NAME = res.data.PROJECT_NAME
                        this.PROJECT_VERSION = res.data.PROJECT_VERSION
                        this.title = res.data.title
                        this.version = res.data.version
                        this.description = res.data.description
                        this.titleVersion = this.title + " (" + this.version + ")"
                        this.noDocText = res.data.noDocText
                        this.hostValue = res.data.host
                        document.title = this.titleVersion
                        let md = "# " + this.titleVersion
                        if (this.description != "") {
                            md += "\n> " + this.description
                        }
                        // this.treeDataNew()
                        // this.treeDataNew = res.data.data
                        this.makeUrlOptions(res.data.data)
                        this.getUrlCache()
                        this.jumpAnchor()
                        this.markdown_text = marked(md)
                        this.loading = false
                    },
                    err => {
                        this.$message.error("Error");
                        this.loading = false
                    }
                )
            },
            make_md(md, con) {
                md += "### url" + "\n"
                var urls = new Array()
                urls = con.url.split(" ")
                if (urls.length == 1) {
                    urls = [urls[0].split("\t")[0]]
                }
                for (i = 0; i < urls.length; i++) {
                    md += "- " + urls[i].replace(/\t/g, " ") + "\n\n"
                }
                if (con.api_type === "api") {
                    md += "### method" + "\n"
                    md += "- " + con.method + "\n\n"
                }
                if (con.doc == this.noDocText && con.doc_md != "") {
                } else {
                    md += "### doc" + "\n"
                    md += "```doc\n" + con.doc + "\n```\n\n"
                }
                return md
            },
            download() {
                let md = ""
                this.treeDataNew.forEach((t, index) => {
                    md += "# " + t.full_name + "\n\n"
                    this.treeData[t.full_name]["children"].forEach((con, index) => {
                        md += "## " + con.name
                        if (con.name_extra != "") {
                            md += "(" + con.name_extra + ")"
                        }
                        md += "\n\n"
                        md = this.make_md(md, con)
                        md += con.doc_md + "\n\n\n"
                    })
                    md += "\n\n"
                })
                saveAs(new Blob([md], {type: "text/markdown;charset=utf-8"}), this.title + " (" + this.version + ")" + ".md")
            },
            treeFilterNode(value, data) {
                if (!value) return true
                let srcStr = data.label.toLowerCase()
                let desStr = value.toLowerCase()
                return srcStr.indexOf(desStr) !== -1
            },
            treeNodeClick(data, node, self) {
                if (data.router != null) {
                    let md = ""
                    md += "# " + data.label + "\n\n"
                    md = this.make_md(md, data)
                    md += data.doc_md
                    this.markdown_text = marked(md)
                    document.querySelectorAll("pre code").forEach((block) => {
                        hljs.highlightElement(block)
                    })
                    this.dropAnchor(node?.parent?.data?.id, data.id)
                }
            },
            debugShow() {
                if (this.debugDisplay === "display:none") {
                    this.debugShowIcon = "el-icon-document"
                    this.debugDisplay = "display:block"
                    this.docDisplay = "display:none"
                    this.setCache("cache:page", "debugger")
                } else {
                    this.debugShowIcon = "el-icon-picture-outline-round"
                    this.debugDisplay = "display:none"
                    this.docDisplay = "display:block"
                    this.setCache("cache:page", "document")
                }
            },
            makeMethodOptions() {
                this.methodList.forEach((m, index) => {
                    this.methodOptions.push({value: m, label: m})
                })
            },
            makeUrlOptions(dataDict) {
                let urlOptionsNew = new Array()
                for (key in dataDict) {
                    dataDict[key]["children"].forEach((cons, i) => {
                        console.log(cons)
                        cons['children'].forEach((con, index) => {
                            let urls = new Array()
                            console.log(con)
                            urls = con.url.split(" ")
                            if (urls.length == 1) {
                                urls = [urls[0].split("\t")[0]]
                            }
                            for (i = 0; i < urls.length; i++) {
                                let url = urls[i].split("\t")[0]
                                if (!urlOptionsNew.some(item => {
                                    if (item.label == url) {
                                        return true
                                    }
                                })) {
                                    urlOptionsNew.push({value: url, label: url})
                                }
                            }
                        })

                    })
                }
                this.urlOptions = urlOptionsNew
            },
            changeOptions(options, optionsBak, val) {
                val = val.trim()
                if (!val) {
                    let optionsNew = new Array()
                    this[options].forEach((item, index) => {
                        if (item.label !== this[optionsBak]) {
                            optionsNew.push({value: item.value, label: item.label})
                        }
                    })
                    this[options] = optionsNew
                    return
                }
                if (val === "") {
                    return
                }
                this[optionsBak] = val
                if (!this[options].some(item => {
                    if (item.label == val) {
                        return true
                    }
                })) {
                    this[options].push({value: val, label: val})
                }
            },
            addHeader() {
                let headerNameInputNew = this.headerNameInput.trim()
                let headerValueInputNew = this.headerValueInput.trim()
                if ((headerNameInputNew !== "") && (headerValueInputNew !== "")) {
                    if (headerNameInputNew.toLowerCase() === "content-type") {
                        headerNameInputNew = headerNameInputNew.toLowerCase()
                        headerValueInputNew = headerValueInputNew.toLowerCase()
                    }
                    type = Math.floor(Math.random() * 5)
                    this.headerTags.push({
                        name: [headerNameInputNew, headerValueInputNew].join(":"),
                        type: this.headerTagsTypeDict[type]
                    })
                    this.setCache("cache:header", this.headerTags)
                }
            },
            delHeader(tag) {
                this.headerTags.splice(this.headerTags.indexOf(tag), 1)
                this.setCache("cache:header", this.headerTags)
            },
            makeHljsPreCode(id, text) {
                document.getElementById(id).innerHTML = "<pre><code>" + text + "</code></pre>"
            },
            makeResponse(res) {
                resStatus = [res.status, res.statusText].join(" ")
                resHeaders = res.headers
                resData = res.data
                let resHeadersdict = Object.assign({"status code": resStatus}, resHeaders)

                this.makeHljsPreCode("responseHeaderText", JSON.stringify(resHeadersdict, null, 4))
                if (resHeaders["content-type"] && resHeaders["content-type"].indexOf("application/json") != -1) {
                    this.makeHljsPreCode("responseResponseText", JSON.stringify(resData))
                    this.makeHljsPreCode("responsePreviewText", JSON.stringify(resData, null, 4))
                } else if (resHeaders["content-type"] && resHeaders["content-type"].indexOf("text/html") != -1) {
                    document.getElementById("responseResponseText").innerHTML = "<code>" + resData + "</code>"
                    document.getElementById("responsePreviewText").innerHTML = "<code>" + resData + "</code>"
                } else {
                    this.makeHljsPreCode("responseResponseText", resData)
                    this.makeHljsPreCode("responsePreviewText", resData)
                }
                document.querySelectorAll("pre code").forEach((block) => {
                    hljs.highlightElement(block)
                })
            },
            sendRequest() {
                if ((this.hostValue === "") || (this.urlValue === "")) {
                    return
                }
                this.loading = true
                headers = {}
                this.headerTags.forEach((item, index) => {
                    headerTag = item.name.split(":")
                    headers[headerTag[0]] = headerTag[1]
                })

                data = ""
                params = {}
                if (this.bodyTextarea !== "") {
                    if (this.methodValue === "GET" || this.methodValue === "DELETE") {
                        this.bodyTextarea.split("&").forEach((item, index) => {
                            params[item.split("=")[0]] = item.split("=")[1]
                        })
                    } else {
                        if (!headers["content-type"] || (headers["content-type"] &&
                            (headers["content-type"].indexOf("application/x-www-form-urlencoded") === -1 &&
                                headers["content-type"].indexOf("multipart/form-data") === -1 &&
                                headers["content-type"].indexOf("text/plain") === -1))) {
                            try {
                                data = JSON.parse(this.bodyTextarea)
                            } catch (err) {
                                data = this.bodyTextarea
                                this.$notify.error({
                                    title: "Error",
                                    message: "The request body is not json"
                                })
                            }
                        } else {
                            data = this.bodyTextarea
                        }
                    }
                }
                this.setBodyCache()

                document.getElementById("responseHeaderText").innerHTML = ""
                document.getElementById("responsePreviewText").innerHTML = ""
                document.getElementById("responseResponseText").innerHTML = ""
                axios({
                    method: this.methodValue,
                    url: this.hostValue + this.urlValue,
                    timeout: 1000 * 30,
                    headers: headers,
                    data: data,
                    params: params
                }).then(res => {
                        this.makeResponse(res)
                        this.$notify({
                            title: "Success",
                            type: "success"
                        })
                        this.loading = false
                    },
                    err => {
                        if (err.response) {
                            errResponse = err.response
                            this.makeResponse(errResponse)
                            this.$notify({
                                title: "Warning",
                                type: "warning"
                            });
                            this.loading = false
                            return
                        } else if (err.request) {
                            console.log(err.request)
                            document.getElementById("responseResponseText").innerHTML = err.request
                        } else {
                            console.log("Error", err.message)
                            document.getElementById("responseResponseText").innerHTML = err.message
                        }
                        document.querySelectorAll("pre code").forEach((block) => {
                            hljs.highlightElement(block)
                        })
                        this.responseTabsActiveName = "Response"
                        this.$notify.error({
                            title: "Error",
                        })
                        this.loading = false
                    }
                )
            },
            setCache(k, v) {
                try {
                    localStorage.setItem(k, JSON.stringify(v));
                } catch (err) {
                    localStorage.setItem(k, v);
                }
            },
            getCache(k) {
                try {
                    cacheValue = JSON.parse(localStorage.getItem(k))
                } catch (err) {
                    cacheValue = localStorage.getItem(k)
                }
                return cacheValue
            },
            setBodyCache() {
                bodyKey = "cache:body:" + sha1([this.hostValue, this.urlValue, this.methodValue].join("-"))
                this.setCache(bodyKey, this.bodyTextarea)
            },
            getHostCache() {
                let hostCacheOptions = this.getCache("cache:host")
                if (hostCacheOptions) {
                    this.hostOptions = hostCacheOptions
                }
            },
            getUrlCache() {
                let urlCacheOptions = this.getCache("cache:url")
                if (urlCacheOptions) {
                    urlCacheOptions.forEach((item, index) => {
                        this.changeOptions("urlOptions", "urlOptionsBak", item.label)
                    })
                }
            },
            getHeaderCache() {
                let headerCacheTags = this.getCache("cache:header")
                if (headerCacheTags) {
                    this.headerTags = headerCacheTags
                }
            },
            getBodyCache() {
                if ((this.hostValue === "") || (this.urlValue === "")) {
                    return
                }
                bodyKey = "cache:body:" + sha1([this.hostValue, this.urlValue, this.methodValue].join("-"))
                let bodyCacheTextarea = this.getCache(bodyKey)
                if (bodyCacheTextarea) {
                    this.bodyTextarea = bodyCacheTextarea
                }
            },
            pageShow() {
                let pageCache = this.getCache("cache:page")
                if (pageCache && pageCache === "debugger") {
                    this.debugShow()
                }
            },
            copyResponse() {
                let text = document.getElementById(this.responseTabsActiveDict[this.responseTabsActiveName]).innerText
                let input = document.createElement("textarea")
                document.body.appendChild(input)
                input.value = text
                input.select();
                if (document.execCommand("copy")) {
                    this.$message({
                        message: "Copied",
                        type: "success"
                    })
                }
                document.body.removeChild(input)
            },
            dropAnchor(parent, anchor) {
                let currentUrl = window.location.href
                let hostUrl = currentUrl.split("#")[0]
                history.pushState("", "", hostUrl + "#" + this.headerIndex + '-' + parent + '-' + anchor)
            },
            jumpAnchor() {
                this.$nextTick(function () {
                    try {
                        let currentUrl = decodeURI(window.location.href)
                        let data = currentUrl.split("#")[1].split("-")
                        let tab = data[0]
                        let parent = data[1]
                        let anchorParent = ''
                        let anchor = data[2]
                        this.headerIndex = tab
                        let apiTree = this.$refs.apiTree[Number(tab)]
                        apiTree.store.nodesMap[parent].expanded = true
                        apiTree.setCurrentKey(anchor)
                        let node = apiTree.getNode(anchor)
                        this.treeNodeClick(node.data, node)
                    } catch (err) {
                        console.log('err', err)
                    }
                })
            }
        },
        watch: {
            treeFilterText(val) {
                this.$refs.apiTree[Number(this.headerIndex)].filter(val)
            },
            methodValue(val) {
                this.getBodyCache()
            },
            hostValue(val) {
                this.changeOptions("hostOptions", "hostOptionsBak", val)
                this.setCache("cache:host", this.hostOptions)
                this.getBodyCache()
            },
            urlValue(val) {
                this.changeOptions("urlOptions", "urlValueBak", val)
                this.setCache("cache:url", this.urlOptions)
                this.getBodyCache()
            }
        }
    })
</script>

</html>